﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting;
using System.Security.Permissions;
using PSIEpics;
using epics_server_remoting_class;
using JEpicsServer_driver;
using JtextEpicsInterface;
using System.Reflection;
using System.IO;
using System.Threading;


//this class wraps all the neccesery functions that is needed to build a IOC on Jtext
//designed to run on windows 7,server 2008 vist ,also works on xp & 2k of couse on x86 arch
//also on wince ARM arch
//with mono it should works on linux and on multi hardware arch(to be tested) 
//this need using remoting cause it can have multi instacne run on one machine
//make sure the epics_server_remoting_server is running

//major changes 2011/01/18
//add interface -IEpicsAccess- to this Lib
//JEpicsServer will implement this interface
//this interface will let others to operate the values of each PV evern pvs oout of this server(TBC)


namespace jtext_EpicsServer
{
    /// <summary>
    /// <para>this class wraps all the neccesery functions that is needed to build a IOC on Jtext
    ///designed to run on windows 7,server 2008 vist ,also works on xp & 2k of couse on x86 arch,
    ///also on wince ARM arch,
    ///with mono it should works on linux and on multi hardware arch(to be tested) .
    ///this need using remoting cause it can have multi instacne run on one machine
    ///make sure the epics_server_remoting_server is running</para>
    ///<para>This also mange server and plugIns for you,use this you can create a IOC in leteraly no time</para>
    /// <seealso cref="JtextEpicsInterface.IJEServer"/>
    /// <seealso cref="epics_server_remoting_class.EpicsServer_Remoting"/>
    /// </summary>
    public class JEpicsServer:IJEServer 
    {

        /// <summary>
        /// the remoting server for this JEServer
        /// <seealso cref="epics_server_remoting_class.EpicsServer_Remoting"/>
        /// </summary>
        protected EpicsServer_Remoting myRemoteserver;  //the remoting server object on ERS(epics remoting server)


        /// <summary>
        /// Wierd,you can only have one instance in one program asambly
        /// </summary>
        public static JEpicsServer myInstance = null;  //use singleton design pattern ,this indecats if JEpicsServer has been created

        
        //hashtable for driver and plugin is needed
        /// <summary>
        /// the hashtable that contains the PlugIns,public for every,so becareful
        /// </summary>
        public Hashtable PlugIns;

        public Hashtable PlugInType;
        
        /// <summary>
        /// the hashtable that contains the Drivers,public for every,so becareful
        /// </summary>
        protected Hashtable Drivers;

        /// <summary>
        /// The server will have the records that set to be flushed by server flushed in this interval.
        /// <seealso cref="jtext_EpicsServer.JEpicsRecord"/>
        /// </summary>
        protected int SvrScanInterval;  //the scan interval of the server
        protected string  SvrName;


        /// <summary>
        /// is the server is running?
        /// <para>Running means scan thread is working</para>
        /// </summary>
        protected bool IsRunning;   //is the server running? the drivers and the plugins are inited than it's running

        /// <summary>
        /// not used
        /// </summary>
        protected bool IsScanning;  //is the scanner thread started


        /// <summary>
        /// Contains all the record both normal record and array record
        /// </summary>
        protected Hashtable JERecordList;

        /// <summary>
        /// Occurs when [on my flush].
        /// <para>the server may flush some record at a certain interval,when flus this's raised</para>
        /// </summary>
        public event FlushRecordHandler OnMyFlush;  ////when server flush the record this event is raised


        /// <summary>
        /// this genarate OnMySelf Flush event at a certain interval
        /// </summary>
        private Timer FlushTimer;   //this genarate OnMySelf Flush event at a certain interval

        /// <summary>
        /// handles the server flush
        /// </summary>
        private TimerCallback FlushDelegate;    //timer will run this delegate


        /// <summary>
        /// Initializes a new instance of the <see cref="JEpicsServer"/> class.
        /// </summary>
        /// <param name="ServerName">Name of the server.</param>
        /// <param name="ScanInterval">The scan interval.</param>
        public JEpicsServer(string ServerName,int ScanInterval)
        {
            //constructor
            //initialize the remoting client
            //create epics_server_remoting
            //if (myInstance != null)
            //{
            //    throw new System.Exception("No multi-instance in one program");
            //}
            //else
            //{
                
                #region ini_remoting
                // 创建一个IPC信道。
                try
                {
                    IpcChannel channel = new IpcChannel();
                    // 注册这个信道。
                    System.Runtime.Remoting.Channels.ChannelServices.RegisterChannel(channel);
                    // 注册一个远程对象的客户端代理.
                    System.Runtime.Remoting.WellKnownClientTypeEntry remoteType = new System.Runtime.Remoting.WellKnownClientTypeEntry(typeof(EpicsServer_Remoting), "ipc://TestChannel/EpicsServer_Remoting.rem");
                    System.Runtime.Remoting.RemotingConfiguration.RegisterWellKnownClientType(remoteType);
                }
                catch
                {
 
                }
                myRemoteserver = new EpicsServer_Remoting();
                #endregion

                JERecordList = new Hashtable();
                JERecordList=new Hashtable ();
                Drivers = new Hashtable();
                PlugIns = new Hashtable();
                PlugInType = new Hashtable();
                SvrScanInterval = ScanInterval;
                SvrName=ServerName;
                IsRunning = false;
                IsScanning = false;
                myInstance = this;

            //}
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// dispose all record
        /// <see cref="JEpicsServer"/> is reclaimed by garbage collection.
        /// </summary>
        ~JEpicsServer()
        {
            this.DisposeAllRecord();
        }

        /// <summary>
        /// Sets the scan interval.Do this before you seart the server
        /// </summary>
        /// <param name="Interval">The scan interval.</param>
        public void setScanInterval(int Interval)
        {
            SvrScanInterval = Interval;
            //handle the scan thread only interval>0 we do scan
        }



        //function below is oly a test version
        /// <summary>
        /// Adds the record.
        /// </summary>
        /// <typeparam name="T">the data type of this record normally we only recomman double</typeparam>
        /// <param name="MyProp">My prop-Recor_prop-.The only parameter,course it contains all the info the a record would have</param>
        public void AddRecord<T>(Record_prop MyProp) //accturelly will take lot more argument
        {

            if (JERecordList.Contains(MyProp.pvName) == true)
            {
                ((JEpicsRecord)JERecordList[MyProp.pvName]).Dispose();
                JERecordList.Remove(MyProp.pvName);
            }
            //add read record property and read record driver property funtion 
            //use the record property to pass right driver to the record
            if (MyProp.DataClass == 0)
            {   //if not an arrayRecord
                if (Drivers.Contains(MyProp.Drv_name) == true)
                {
                    JEpicsRecord<T> TempRecord = new JEpicsRecord<T>(this.myRemoteserver, MyProp, ((IJEDriver)Drivers[MyProp.Drv_name]), this);   //modify this 
                    JERecordList.Add(MyProp.pvName, TempRecord);
                }
                else
                {
                    //No driver
                    JEpicsRecord<T> TempRecord = new JEpicsRecord<T>(this.myRemoteserver, MyProp, null, this);   //modify this 
                    JERecordList.Add(MyProp.pvName, TempRecord);
                }
            }
            else 
            {   //if is an arrayRecord
                if (Drivers.Contains(MyProp.Drv_name) == true)
                {
                    JEpicsArray<T> TempRecord = new JEpicsArray<T>(this.myRemoteserver, MyProp, ((IJEDriver)Drivers[MyProp.Drv_name]), this);//modify this 
                    JERecordList.Add(MyProp.pvName, TempRecord);
                }
                else
                {
                    //No driver
                    JEpicsArray<T> TempRecord = new JEpicsArray<T>(this.myRemoteserver, MyProp, null, this);  //modify this 
                    JERecordList.Add(MyProp.pvName, TempRecord);
                }
            }
        }


        /// <summary>
        /// Adds the record.you can speicify dataType in record_prop
        /// </summary>
        /// <param name="MyProp">My prop.</param>
        public void AddRecord(Record_prop MyProp) //accturelly will take lot more argument
        {
                switch (MyProp.DataType)
                {
                    case CommonConst.DT_DOUBLE:
                        {
                            this.AddRecord<double>(MyProp);
                            break;
                        }
                    case CommonConst.DT_INT:
                        {
                            this.AddRecord<int>(MyProp);
                            break;
                        }
                    case CommonConst.DT_SBYTE:
                        {
                            this.AddRecord<sbyte>(MyProp);
                            break;
                        }
                    case CommonConst.DT_INT16:
                        {
                            this.AddRecord<short>(MyProp);
                            break;
                        }
                    case CommonConst.DT_INT64:
                        {
                            this.AddRecord<long>(MyProp);
                            break;
                        }

                    default:
                        {
                            this.AddRecord<double>(MyProp);
                            break;
                        }
                }
           
        }




        #region Initilalize
        //ini the server her
        //first load driver from files then instancelize them and put in hashtable
        //then ini the driver the whole hardware part
        //the load plugin from files and instancelize them and to hash table 
        //ini plugin
        //then the driver is started

        /// <summary>
        /// Loads the plug ins.
        /// </summary>
        /// <param name="PluginPath">The plugin path.</param>
        /// <returns></returns>
        public int LoadPlugIns(string PluginPath)
        {
            //this will return the numbers of the drivers 
            int PluginNum = 0;
            string[] files = Directory.GetFiles(PluginPath);
            foreach (string file in files)
            {
                string ext = file.Substring(file.LastIndexOf("."));
                if (ext != ".dll") continue;
                Assembly pluginAsm = Assembly.LoadFile(file);   //extract the assembly ftom the file
                Type[] pluginTypes = pluginAsm.GetTypes();   //get the types in this assembly
                foreach (Type pluginType in pluginTypes)
                {
                    if (IsPlugIn(pluginType) == true)
                    {
                        IJEPlugIn pluginInst = ((IJEPlugIn)pluginAsm.CreateInstance(pluginType.FullName));
                        PlugIns.Add(pluginInst.PlugInName, pluginAsm);   //add the plugin asm  to hastable
                        PlugInType.Add(pluginInst.PlugInName, pluginType.FullName);
                        Console.WriteLine("PlugIn: " + pluginInst.PlugInName);
                        PluginNum++;
                    }
                }

            }
            return PluginNum;

        }


        /// <summary>
        /// Determines whether the specified TPLugin  is plugin.
        /// </summary>
        /// <param name="TDrv">The TPligIn.a tyoe that may be a Plugin</param>
        /// <returns>
        ///   <c>true</c> if the specified TPligIn is plugin; otherwise, <c>false</c>.
        /// </returns>
        protected static bool IsPlugIn(Type TPligIn)
        {
            //return the Tdriv has the IJEPlugIn interface
            Type[] interfaces = TPligIn.GetInterfaces();
            foreach (Type theInterface in interfaces)
            {
                if (theInterface.FullName == "JtextEpicsInterface.IJEPlugIn")
                {
                    return true;
                }
            }
            return false;
        }

        //protected void IniAllPlugIn()
        //{
        //    //init and run all the drivers
        //    if (PlugIns.Count == 0) return;
        //    foreach (DictionaryEntry PlugIn in PlugIns)
        //    {
        //        ((IJEPlugIn)(PlugIn.Value)).IniPlugIn(null); //init the hardware of the drivers
        //    }
        //}



        /// <summary>
        /// Loads the drivers.
        /// </summary>
        /// <param name="DriverPath">The driver path.folder path that that contains the driver dlls</param>
        /// <returns></returns>
        public int LoadDrivers(string DriverPath)
        {
            //this will return the numbers of the drivers 
            int DrvNum=0;
            string[] DRVfiles = Directory.GetFiles(DriverPath);
            foreach( string DRVFile in DRVfiles ) 
            {
                string ext = DRVFile.Substring(DRVFile.LastIndexOf("."));
                if (ext != ".dll") continue;
                Assembly DrvAsm = Assembly.LoadFile(DRVFile);   //extract the assembly ftom the file
                Type[] DrvTypes=DrvAsm.GetTypes();   //get the types in this assembly
                foreach (Type DrvType in DrvTypes)
                {
                    if (IsDriver(DrvType) == true)
                    {
                        IJEDriver DrvInst = ((IJEDriver)DrvAsm.CreateInstance(DrvType.FullName));
                        Drivers.Add(DrvInst.DriverName, DrvInst);   //add the drv to hastable
                        Console.WriteLine("Driver: " + DrvInst.DriverName);
                        DrvNum++;
                    }
                }

            }
            return DrvNum;

        }

        /// <summary>
        /// Determines whether the specified TDRV is driver.
        /// </summary>
        /// <param name="TDrv">The TDRV.a tyoe that may be a driver</param>
        /// <returns>
        ///   <c>true</c> if the specified TDRV is driver; otherwise, <c>false</c>.
        /// </returns>
        protected static  bool IsDriver(Type TDrv)
        {
            //return the Tdriv has the IJEDriver interface
            Type[] interfaces = TDrv.GetInterfaces();
            foreach (Type theInterface in interfaces)
            {
                if (theInterface.FullName == "JtextEpicsInterface.IJEDriver")
                {
                    return true;   
                }
            }
            return false;
        }

        /// <summary>
        /// Ini all Drivers.-IniHard for the driver-
        /// <seealso cref="JtextEpicsInterface.JEDriver"/>
        /// </summary>
        protected  void IniAllDrv()
        { 
            //init and run all the drivers
            if (Drivers.Count == 0) return;
            foreach (DictionaryEntry Drv in Drivers)
            {
                ((IJEDriver)(Drv.Value)).IniHard(null); //init the hardware of the drivers
            }   
        }



        /// <summary>
        /// Starts all DRV.
        /// </summary>
        protected void StartAllDrv()
        {
            //init and run all the drivers
            if (Drivers.Count == 0) return;
            foreach (DictionaryEntry Drv in Drivers)
            {
                ((IJEDriver)(Drv.Value)).StartDriver(null); //init the hardware of the drivers
            }

        }


        /// <summary>
        /// Initializes all record.
        /// <para></para>
        /// </summary>
        protected void IniAllRecord()
        {

            if (IsRunning == true) return;
            if (JERecordList.Count == 0) return;
            foreach (DictionaryEntry JERecord in JERecordList)
            {
                //((JEpicsRecord)JERecord.Value).IniRecordDriver();
                ((JEpicsRecord)JERecord.Value).IniRecord(); //this well ini the record driver
            }

        }



        /// <summary>
        /// Starts all record.
        /// </summary>
        protected  void StartAllRecord()
        {
            
            if (IsRunning == true) return;
            if (JERecordList.Count  == 0) return;
            foreach (DictionaryEntry JERecord in JERecordList)
            {
                //((JEpicsRecord)JERecord.Value).IniRecordDriver();
                ((JEpicsRecord)JERecord.Value).StartRecord(); //this well ini the record driver
            }

        }

        /// <summary>
        /// Starts the server.
        /// <para>start the driver,then start record ,then if server has a scanner interval, start scann thread</para>
        /// </summary>
        /// <returns></returns>
        public bool StartServer()
        {
            //this will start the server,driver ,starts all the record
            this.IniAllDrv();
            this.IniAllRecord();
            this.StartAllDrv();
            this.StartAllRecord();
            if (ScanInterval > 0)
            {
                //create and start the scanner thread here
                FlushDelegate = new TimerCallback(SelfFlush);
                FlushTimer = new Timer(FlushDelegate, null, ScanInterval, ScanInterval);
                OnMyFlush += new FlushRecordHandler(this.BenchFlush); //other if want to konw when this recor get flushed can also regsit this event
            }
            IsRunning = true;
            return IsRunning;
        }

        /// <summary>
        /// Kills all Drivers.
        /// </summary>
        public void KillAllDrv()
        {
            //stop all the drvivers but not remove from the hashtable
            if (Drivers.Count == 0) return;
            foreach (object Drv in Drivers)
            {
                ((IJEDriver)Drv).KillHard(null ); //kill the hardware of the drivers    //usually take no arguments
            }
        }

        #endregion



        /// <summary>
        /// Disposes the record.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="pvName">Name of the pv.</param>
        public void DisposeRecord<T>(string pvName)
        {
            if (JERecordList.Contains(pvName) == true)
            {
                ((JEpicsRecord<T>)JERecordList[pvName]).Dispose();
            }
        }

        /// <summary>
        /// Disposes all record.
        /// </summary>
        public void DisposeAllRecord()
        {
            //dispoes all record not array record
            foreach (object JERecord in JERecordList)
            {
                
                ((JEpicsRecord)JERecord).Dispose();

            }
        }


        /// <summary>
        /// Gets the <see cref="T:JtextEpicsInterface.IJERecord"/> with the specified pv name.
        /// <para>for the out side-most the case,the plugins- who want to access the record </para>
        /// </summary>
        public IJERecord this[string pvName]
        {   //index of the record
            get
            {
                if (JERecordList.Contains(pvName) == true)
                {
                    return ((IJERecord)JERecordList[pvName]);
                }
                else
                {
                    return null;
                    //??you refer to a non exist record
                }
            }
        }

        /// <summary>
        /// Gets the record.
        /// <para>this will return the real recor ,you will have great power over this record ,so becareful</para>
        /// </summary>
        /// <param name="pvName">Name of the pv.</param>
        /// <returns></returns>
        public JEpicsRecord getRecord(string pvName)
        {
            if (JERecordList.Contains(pvName) == true)
            {
                return ((JEpicsRecord)JERecordList[pvName]);
            }
            else
            {
                return null;
                //??you refer to a non exist record
            }
        }

        
        #region array

        //public void AddArrayRecord<T>(string pvName,int len)
        //{

        //    if (JERecordList.Contains(pvName) == true)
        //    {
        //        ((JEpicsArray<T>)JERecordList[pvName]).Dispose();
        //        JERecordList.Remove(pvName);
        //    }
        //    JEpicsArray<T> TempRecord = new JEpicsArray<T>(pvName, len ,this.myRemoteserver);
        //    JERecordList.Add(pvName, TempRecord);

        //}

        //public void DisposeArrayRecord<T>(string pvName)
        //{
        //    if (JERecordList.Contains(pvName) == true)
        //    {
        //        ((JEpicsArray<T>)JERecordList[pvName]).Dispose();
        //    }
        //}

        //public void DisposeAllArrayRecord()
        //{
        //    foreach (object JERecord in JERecordList)
        //    {

        //        ((jtext_EpicsArrayRecord_ABS)JERecord).Dispose();

        //    }
        //}

        //public JEpicsArray<T> GetArraryRecord<T>(string pvName)
        //{
        //    if (JERecordList.Contains(pvName) == true)
        //    {
        //        return ((JEpicsArray<T>)JERecordList[pvName]);
        //    }
        //    else
        //    {
        //        return null;
        //        //??you refer to a non exist record
        //    }
        //}

        #endregion

        #region flush
        /// <summary>
        /// Flush all record that is set to server flush.
        /// </summary>
        public void BenchFlush()
        {
            foreach (DictionaryEntry JERecord in JERecordList)
            {
                //add if (scan status??)
                //if running and scan by the server
                if (((JEpicsRecord)JERecord.Value).Running==true && ((JEpicsRecord)JERecord.Value).myRecordProp.SCANINTERVAL==0)
                {   
                    ((JEpicsRecord)JERecord.Value).BigFlush();
                }
            }
        }


        /// <summary>
        /// It's time for server to flush itself ,rais event  OnMyFlush
        /// </summary>
        /// <param name="inPut">The in put.</param>
        private void SelfFlush(object inPut)    //the argument is not used ,just rais event
        {
            if (OnMyFlush != null)
            {
                OnMyFlush();    //rais event
            }
        }

        #endregion


        #region properties
        /// <summary>
        /// Gets a value indicating whether this <see cref="JEpicsServer"/> is running.
        /// </summary>
        /// <value>
        ///   <c>true</c> if running; otherwise, <c>false</c>.
        /// </value>
        public bool Running
        {
            get { return IsRunning; }
        }

        public bool Scanning
        {
            get { return IsScanning; }
        }

        /// <summary>
        /// Gets the server scan interval.
        /// </summary>
        public int ScanInterval
        {
            get { return SvrScanInterval; }
        }

        /// <summary>
        /// Gets the server name.
        /// </summary>
        public string Name
        {
            get { return SvrName; }
        }

        public Hashtable DeviceTable { get; set; }

        #endregion

    }
}
